# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
## NPSolitaire:
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## from PySolFC code:
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##--------------------------------------------------------------------------##
"""
Game mix-in class for bind event support. Handles mouse and canvas events.
"""
__docformat__ = 'restructuredtext en'

import abc
import logging

LOGGER = logging.getLogger('PySolFC.GameLib.BindMixin')

from ..settings import CONF_OPT
from ..viewlib import constants as vcnst
from . import state


class GameBind(object):
    """
    Handle game events.
    """
    __metaclass__ = abc.ABCMeta
    __resize_callback = None
    canvas = app = drag = None

    @abc.abstractmethod
    def demo_stop(self):
        """
        .. demo_stop -> bool

        Stub to enter or leave demo mode. Satisfied with demo module in library.
        """
        pass

    @abc.abstractmethod
    def auto_play(self, flip=True, drop=True, deal=True, sound=True):
        """
        Handle auto flip, drop, and deal/play.

        .. \auto_play(flip:bool=AutoFaceupOption, drop:bool=AutoDropOption,
             deal:bool=AutoDealOption, sound:True=True) -> bool

        Stub to handle automatic moves. Satisfied with move module in library.
        """
        pass

    @abc.abstractmethod
    def redo(self, sound=False):
        """
        redo([sound:bool=False]) -> None

        Stub to redo current move. Satisfied with move module in library.
        """
        pass

    @abc.abstractmethod
    def undo(self, sound=False):
        """
        undo([sound:bool=False]) -> None

        Stub to undo current move. Satisfied with move module in library.
        """
        pass

    @abc.abstractmethod
    def _timer_pause(self, pause=True):
        """
        _timer_pause(pause:bool=True) -> None

        Stub to enter or leave pause mode. Satisfied with module in library.
        """
        pass

    @abc.abstractmethod
    def _hide_stack_tip(self):
        """
        _hide_stack_tip() -> bool

        Stub to hide stack tips. Satisfied with module in library.
        """
        pass

    @abc.abstractmethod
    def _view_resize(self, scale=None):
        """
        _view_resize([scale:Scale=ScaleConfig]) -> None

        Stub to resize table. Satisfied with game module in library.
        """
        pass

    @abc.abstractproperty
    def is_demo(self):
        """
        is_demo -> None

        Stub to return demo state. Satisfied with demo module in library.
        """
        pass

    def __init__(self):
        """
        __init__() -> None

        Initialize bind mix-in subclass then call next ancestor.

        *Variables*:
          event_handled :
            Set False to continue event.
        """
        self.event_handled = False
        super(GameBind, self).__init__()

    def init_bindings(self):
        """
        init_bindings() -> None

        Bind events on canvas. May override method, but should bind canvas only.

          <Button-1>:
            __undo_handler(event) - Undo move
          <Button-2>:
            __drop_handler(event) - Drop card
          <Button-3>:
            __redo_handler(event) - Redo Move
          <Unmap>:
            __unmap_handler(event) - Pause on minimize
          <Configure>:
            _config_handler(event) - Adjust stacks on resize
        """
        self.canvas.bind('<1>', self.__undo_handler)
        self.canvas.bind('<2>', self.__drop_handler)
        self.canvas.bind('<3>', self.__redo_handler)
        self.canvas.bind('<Unmap>', self.__unmap_handler)
        self.canvas.bind('<Configure>', self._config_handler, add=True)

    def __default_handler(self, event):
        """
        __default_handler(event:Event) -> bool

        Handle canvas events. Called by drop handler, undo handler and redo
        handler to bring games into a playable state before an event.
        """
        if not any([CONF_OPT['mouse_undo'], self.event_handled,
                    self._hide_stack_tip()]):
            return False
        # resume game play
        if self.is_pause:
            self._timer_pause(False)
            self.top.event_generate('<<update_menus>>')
            return False
        # stop demo play
        if self.is_demo:
            self.demo_stop()
            return False
        # cancel drag event
        if not self.event_handled and self.drag['stack']:
            self.drag['stack']._move_cancel_drag(event)
            return False
        # continue event
        return not self.event_handled

    def __drop_handler(self, event):
        """
        __drop_handler(event:Event) -> State

        Handle drop events. Perform drop when default handler then propagate.

        *Variables*:
          event_handled :
            Set False to continue event.
        """
        if self.__default_handler(event):
            self.auto_play(flip=False)
        self.event_handled = False
        return vcnst.EVENT_PROPAGATE

    def __undo_handler(self, event):
        """
        __undo_handler(event:Event) -> State

        Handle undo events. Perform undo when default handler then propagate.

        *Variables*:
          event_handled :
            Set False to continue event.
        """
        if self.__default_handler(event):
            self.undo(sound=True)
        self.event_handled = False
        return vcnst.EVENT_PROPAGATE

    def __redo_handler(self, event):
        """
        __redo_handler(event:Event) -> EventState

        Handle redo events. Perform redo when default handler then propagate.

        *Variables*:
          event_handled :
            Set False to continue event.
        """
        if self.__default_handler(event):
            self.redo(sound=True)
        self.event_handled = False
        return vcnst.EVENT_PROPAGATE

    def __unmap_handler(self, event=None):
        """
        __unmap_handler([event:Event]) -> None

        Handle unmap events. Perform do pause when window unmaps.
        """
        self._timer_pause()

    @state.not_state('is_preview')
    def _config_handler(self, event=None):
        """
        _config_handler([event:Event]) -> None

        Handle canvas config event. Resize canvas when auto scale config option.

        *Variables*:
          __resize_callback :
            Set callback to __resize_handler() callback event.
        """
        if self.canvas is None or not CONF_OPT['scale_auto']:
            return
        if self.__resize_callback:
            self.canvas.after_cancel(self.__resize_callback)
        self.__resize_callback = self.canvas.after(250, self.__resize_handler)

    def __resize_handler(self):
        """
        __resize_handler() -> None

        Handle resize callback. Call resize view to adjust canvas.

        *Variables*:
          __resize_id :
            Unset callback event.
        """
        self.__resize_callback = None
        self._view_resize()